package com.mika.music.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mika.music.constants.Constant;
import com.mika.music.model.Music;
import com.mika.music.model.User;
import com.mika.music.service.*;
import com.mika.music.utils.Mp3Util;
import com.mika.music.utils.ResponseBodyMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.Duration;
import java.util.*;

@Slf4j
@RestController
@RequestMapping("/music")
public class MusicController {
    @Autowired
    private MusicService musicService;

    @Autowired
    private LoveMusicService loveMusicService;

    @Autowired
    private CommentService commentService;

    @Autowired
    private ListenService listenService;

    @Autowired
    private MusicLabelService musicLabelService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Value("${music.local.path}")
    private String SAVE_PATH;

    // 音乐 上传到服务器+上传到数据库, 还需要将对应的音乐标签也上传到数据库中
    @RequestMapping("/upload")
    public ResponseBodyMessage<Boolean> addMusic(@RequestParam String singer, @RequestParam("filename") MultipartFile file,
                                                 @RequestParam("tags[]") List<String> tags, HttpServletRequest request, HttpServletResponse resp) throws IOException {
        // 检查是否登录
        HttpSession session = request.getSession(false);

        if (session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
            return new ResponseBodyMessage<>(-2, "请登录后再操作", false);
        }
        log.info(singer);
        if (!StringUtils.hasLength(singer) || file == null) {
            return new ResponseBodyMessage<>(-1, "请输入歌手后再来操作！", false);
        }
        log.info("接收参数 tags: " + tags);
        // 上传到服务器
        // 获取文件的绝对路径
        String fileNameAndType = file.getOriginalFilename();
        log.info("获取路径，fileNameAndType: " + fileNameAndType);
        if (!StringUtils.hasLength(fileNameAndType)) {
            return new ResponseBodyMessage<>(-1, "请输入歌手后再来操作！", false);
        }
        String path = SAVE_PATH + fileNameAndType;
        // 获取曲名
        int point = fileNameAndType.lastIndexOf(".");
        String title = fileNameAndType.substring(0, point);
        // 查询数据库里是否存在这首曲子，如果存在，则看看该曲子的歌手，与本次上传的曲子歌手是否相同
        // 不同就可以上传，相同就提示用户，这首曲子已经存在，不能进行上传
        // 如果库里不存在这首曲子，那就可以上传
        Music music = musicService.getSongByName(title);
        // 但是这里有一个问题，那就是当上传同一首音乐，并且歌名相同时，就需要提醒用户，歌曲重名了，不能上传
        // 否则当删除该文件时，另一个同名歌曲也会被删除
        // 也就是说，可以上传重复歌曲，但是歌曲的名字不能相同，得与之前同名歌曲名字做一点区分
        if (music != null && music.getSinger().equals(singer)) {
            return new ResponseBodyMessage<>(-1, "很抱歉，曲库中已经存在这首同名同歌手的歌曲，不能进行上传", false);
        } else if (music != null && music.getTitle().equals(title)) {
            return new ResponseBodyMessage<>(-1, "很抱歉，请您上传的曲子名字与其他曲子同名，请对曲名做出一些区分后再进行上传", false);
        }
        // 可以上传，新增歌曲


        File dest = new File(path);
        if (!dest.exists()) {
            // 如果目录不存在，就创建目录
            dest.mkdir();
        }
        try {
            // 上传文件
            file.transferTo(dest);
        } catch (IOException e) {
            log.error(e.getMessage());
            return new ResponseBodyMessage<>(-1, "服务器上传失败，请联系管理员", false);
        }
        // 判断该文件是否是歌曲
        try {
            // TODO 如果该歌曲后缀名不是 MP3，但也是歌曲，那该怎么判断呢？
            // TODO 比如后缀为 .mp3  .m4a   .wav  .flac   .ape 那该怎么判断呢?
            if (!Mp3Util.isMp3File(path)) {
                dest.delete();
                return new ResponseBodyMessage<>(-1, "该文件不是mp3格式", false);
            }
        } catch (Exception e) {
            dest.delete();
            log.error(e.getMessage());
            return new ResponseBodyMessage<>(-1, "该文件不是mp3格式", false);
        }

        // 数据库上传存路径时，title(歌名) 没有加后缀.mp3
        // 注意将音乐标签一并上传，就需要音乐 id 以及标签，所以在上传音乐时
        // 需要获取新增音乐的 id。
        String url = "/music/get?path=" + title;
        Integer result = -1;
        User user = (User) request.getSession().getAttribute(Constant.USERINFO_SESSION_KEY);
        try {
            Music uploadMusic = new Music(title, singer, url, user.getId());
            result = musicService.addMusic(uploadMusic);
            if (result > 0) {
                // 遍历 tags，将音乐标签一个个添加到数据库中
                for (String label : tags) {
                    // 将音乐标签也添加到 redis 中
                    String redisMusicLabel = "musicLabel:music:" + uploadMusic.getId();
                    stringRedisTemplate.opsForSet().add(redisMusicLabel, label);
                    stringRedisTemplate.expire(redisMusicLabel, Duration.ofDays(60));
                    Integer tmp = musicLabelService.addMusicLabel(uploadMusic.getId(), label);
                    stringRedisTemplate.opsForSet().add(redisMusicLabel, label);
                    stringRedisTemplate.expire(redisMusicLabel, Duration.ofDays(60));
                }
                return new ResponseBodyMessage<>(200, null, true);
            } else {
                dest.delete();
                return new ResponseBodyMessage<>(-1, "数据库上传失败，请联系管理员", false);
            }
        } catch (Exception e) {
            dest.delete();
            log.error(e.getMessage());
            return new ResponseBodyMessage<>(-1, "数据库上传失败，请联系管理员", false);
        }

    }


    // 播放音乐时，路径为 /music/get?path=xxx.mp3
    @RequestMapping("/get")
    public ResponseEntity<byte[]> getMusic(String path, HttpServletRequest request) {

        Integer index = path.lastIndexOf(".");
        String musicName = path.substring(0, index);
        HttpSession session = request.getSession(false);
        log.info("接收参数 musicName: " + musicName);
        if (!StringUtils.hasLength(musicName) || session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
            return ResponseEntity.badRequest().build();
        }
        // 根据音乐名来查询音乐详情
        String musicname = "musicName:" + musicName;
        // 查找 musicId
        String id = stringRedisTemplate.opsForValue().get(musicname);
        log.info("getMusic, 从 redis 中查找到 musicId：" + id);
        if (StringUtils.hasLength(id)) {
            // 如果找到了，那就继续找音乐本体
            String musicKey = "music:info:" + id;
            try {
                Music music = objectMapper.readValue(stringRedisTemplate.opsForValue().get(musicKey), Music.class);
                log.info("getMusic, 从 redis 中查找到 music: " + music);
                // 新增用户此次的听歌记录

                User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
                Integer result = listenService.addListenRecord(user.getId(), music.getId());
                log.info("新增音乐记录: " + result);
                // TODO 往 redis 添加："user:1:listenmusic:27"  "27"   过期时间:30 天
                String redisKey = "user:" + user.getId() + ":listenmusic:" + id;
                stringRedisTemplate.opsForValue().set(redisKey, id);
                stringRedisTemplate.expire(redisKey, Duration.ofDays(30));
                log.info("往 redis 中添加听歌记录: " + redisKey);

                File file = new File(SAVE_PATH + path);
                byte[] arr = null;
                arr = Files.readAllBytes(file.toPath());
                if (arr == null) {
                    return ResponseEntity.badRequest().build();
                }
                return ResponseEntity.ok(arr);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }

        // 到这里说明在 redis 里没找到音乐
        Music music = musicService.getSongByName(musicName);
        if (music == null) {
            return ResponseEntity.badRequest().build();
        }
        // 新增用户此次的听歌记录
        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
        Integer result = listenService.addListenRecord(user.getId(), music.getId());
        log.info("getMusic, 新增音乐记录: " + result);
        // TODO 往 redis 添加："user:1:listenmusic:27"  "27"   过期时间:30 天
        String redisKey = "user:" + user.getId() + ":listenmusic:" + music.getId();
        stringRedisTemplate.opsForValue().set(redisKey, String.valueOf(music.getId()));
        stringRedisTemplate.expire(redisKey, Duration.ofDays(30));
        log.info("往 redis 中添加听歌记录: " + redisKey);

        try {
            // 将对应音乐存入 redis 中
            String musicKey = "music:info:" + music.getId();
            stringRedisTemplate.opsForValue().set(musicKey, objectMapper.writeValueAsString(music), Duration.ofDays(60));
            stringRedisTemplate.opsForValue().set(musicname, String.valueOf(music.getId()), Duration.ofDays(60));
            log.info("getMusic, 音乐不在 redis 中存在，需要新增音乐到 redis 中：" + music);
        } catch (JsonProcessingException e) {
            log.error(e.getMessage());
        }

        File file = new File(SAVE_PATH + path);
        byte[] arr = null;
        try {
            arr = Files.readAllBytes(file.toPath());
            if (arr == null) {
                return ResponseEntity.badRequest().build();
            }
            return ResponseEntity.ok(arr);

        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return ResponseEntity.badRequest().build();
    }

    // 不仅要从数据库中删除，还要从本地服务器中删除，要是歌曲被删除了，对应的评论也得被删除,
    // TODO 还得删除音乐标签  听歌记录
    @RequestMapping("/delete")
    public ResponseBodyMessage<Boolean> delete(Integer id, HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return new ResponseBodyMessage<>(-2, "您尚未登录，请登录后再来操作", false);
        }
        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
        if (id == null || id < 1) {
            return new ResponseBodyMessage<>(-1, "id 不合法", false);
        }
        Music music = musicService.getMusicById(id);
        if (music == null) {
            return new ResponseBodyMessage<>(-1, "没有你要删除的歌曲", false);
        }
        //  根据 ID 删除音乐表中的记录
        //  但是首先得根据 ID 获取到音乐名，
        if (music.getUserId() != user.getId()) {
            return new ResponseBodyMessage<>(-1, "这首歌不是您上传的，不能删除", false);
        }
        Integer result = musicService.deleteById(id);
        if (result > 0) {
            // TODO 还得删除音乐标签  听歌记录
            // TODO 根据音乐 id 来删除音乐标签，以及根据 id 来删除听歌记录
            // TODO musicName:音乐名   以及  music:info:<id>
            // TODO 可以从 redis 中删除："user:1:listenmusic:27"  "27"   过期时间:30 天
            // TODO 获取到音乐 ID 后，就能去 redis 中删除对应音乐的标签 "musicLabel:music:27"  "二次元", "摇滚", "动漫"
            // TODO 删除收藏记录 key:"lovedMusic:user:1:music:*蒼月的懺悔詩*"  value:"37"
            String redisListenRecord = "user:" + user.getId() + ":listenmusic:" + music.getId();
            String redisMusicLabel = "musicLabel:music:" + music.getId();
            String redisMusic = "music:info:" + music.getId();
            String redisMusicName = "musicName:" + music.getTitle();
            String redisLovedMusic = "lovedMusic:user:*:music:" + music.getTitle();
            List<String> deleteRedisRecord = new ArrayList<>();
            deleteRedisRecord.add(redisListenRecord);
            deleteRedisRecord.add(redisMusicLabel);
            deleteRedisRecord.add(redisMusic);
            deleteRedisRecord.add(redisMusicName);
            deleteRedisRecord.add(redisLovedMusic);
            stringRedisTemplate.delete(deleteRedisRecord);


            Integer ret = loveMusicService.deleteLovedByMusicId(id);
            Integer commentRet = commentService.deleteCommentByMusicId(id);
            Integer musicLabelRet = musicLabelService.deleteMusicLabelByMusicId(id);
            Integer listenRecordRet = listenService.deleteListenRecordByMusicId(id);
            // 数据库音乐路径为 /music/get?path=xxx.mp3
            // 还得从服务器中删除音乐
            Integer index = music.getUrl().indexOf("=");
            String url = music.getUrl().substring(index + 1);
            String path = SAVE_PATH + url + ".mp3";
            File file = new File(path);
            boolean delete = file.delete();
            // 多删除一次，保证数据一致性
            stringRedisTemplate.delete(deleteRedisRecord);

            if (delete) {
                return new ResponseBodyMessage<>(200, null, true);
            } else {
                return new ResponseBodyMessage<>(-1, "删除失败, 请联系管理员", false);
            }

        } else {
            return new ResponseBodyMessage<>(-1, "删除失败, 请联系管理员", false);
        }

    }


    @RequestMapping("/deleteSel")
    public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("id[]") List<Integer> id, HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return new ResponseBodyMessage<>(-2, "您尚未登录，请登录后再来操作", false);
        }
        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
        // 批量删除音乐也同理，是自己上传的曲子才能删除，不是自己上传的曲子就不能删除
        if (id == null || id.size() <= 0) {
            log.error("ids: " + id);
            return new ResponseBodyMessage<>(-1, "请选中歌曲后再来删除", false);
        }
        //  先根据曲子 ID，查询所有的曲子详情，然后再来一一对比 userID 与当前用户的 ID 是否相同
        //  如果相同，那就删除该歌曲，如果不相同，那就跳过，如果最后一首歌都没删除，则提示用户选中的歌曲不是您上传的，都不能删除
        List<Music> musicList = musicService.getMusicsByIds(id);
        if (musicList == null || musicList.size() <= 0) {
            return new ResponseBodyMessage<>(-1, "没有你要删除的歌曲", false);
        }
        Integer count = 0;
        for (int i = 0; i < musicList.size(); i++) {
            // 看看当前歌曲的上传者与当前用户是否相同
            Music music = musicList.get(i);
            if (music.getUserId() == user.getId()) {
                // 可以删除
                // TODO 还得删除音乐标签  听歌记录
                // TODO 根据音乐 id 来删除音乐标签，以及根据 id 来删除听歌记录
                // TODO musicName:音乐名   以及  music:info:<id>
                // TODO 可以从 redis 中删除："user:1:listenmusic:27"  "27"   过期时间:30 天
                // TODO 获取到音乐 ID 后，就能去 redis 中删除对应音乐的标签 "musicLabel:music:27"  "二次元", "摇滚", "动漫"
                // TODO 删除收藏记录 key:"lovedMusic:user:1:music:*蒼月的懺悔詩*"  value:"37"
                String redisListenRecord = "user:" + user.getId() + ":listenmusic:" + music.getId();
                String redisMusicLabel = "musicLabel:music:" + music.getId();
                String redisMusic = "music:info:" + music.getId();
                String redisMusicName = "musicName:" + music.getTitle();
                String redisLovedMusic = "lovedMusic:user:*:music:" + music.getTitle();
                List<String> deleteRedisRecord = new ArrayList<>();
                deleteRedisRecord.add(redisListenRecord);
                deleteRedisRecord.add(redisMusicLabel);
                deleteRedisRecord.add(redisMusic);
                deleteRedisRecord.add(redisMusicName);
                deleteRedisRecord.add(redisLovedMusic);
                stringRedisTemplate.delete(deleteRedisRecord);


                // 删除该歌曲，从数据库删除，删除收藏列表，删除本地文件,删除关于这首歌的评论
                Integer result1 = musicService.deleteById(music.getId());
                Integer result2 = loveMusicService.deleteLovedByMusicId(music.getId());
                Integer result3 = commentService.deleteCommentByMusicId(music.getId());
                // 还得删除音乐标签  听歌记录
                // 根据音乐 id 来删除音乐标签，以及根据 id 来删除听歌记录
                Integer musicLabelRet = musicLabelService.deleteMusicLabelByMusicId(music.getId());
                Integer listenRecordRet = listenService.deleteListenRecordByMusicId(music.getId());
                Integer index = music.getUrl().indexOf("=");
                String url = music.getUrl().substring(index + 1);
                String path = SAVE_PATH + url + ".mp3";
                File file = new File(path);
                boolean delete = file.delete();

                // 多删除一次，保证数据一致性
                stringRedisTemplate.delete(deleteRedisRecord);
                if (result1 > 0 && delete) {
                    count++;
                }
            }
        }
        if (count > 0) {
            // 删除所有你选中的歌曲并且该歌曲是你之前上传的。
            return new ResponseBodyMessage<>(200, null, true);
        } else {
            return new ResponseBodyMessage<>(-1, "很抱歉，您所选中的歌曲都不是您上传的，不能进行删除", false);
        }

    }

    @RequestMapping("/getMusics")
    public ResponseBodyMessage<List<Music>> getMusics(String musicName) {
        if (!StringUtils.hasLength(musicName)) {
            // 音乐名为空，查询所有音乐
            List<Music> result = musicService.getMusics();
            return new ResponseBodyMessage<>(200, null, result);
        }
        // 根据音乐名，去 redis 中查找多个音乐 ID
        // 然后再去 redis 中查找音乐本体，找不到再去 mysql 查询
        // 根据音乐名来查询音乐详情
        List<String> keys = new ArrayList<>();
        String patterns = "musicName:" + "*" + musicName + "*";
        log.info("getMusics, 根据样式: " + patterns + "去 redis 中扫描符合要求的 keys");
        // 指定样式以及每次扫描的数量
        ScanOptions scanOptions = ScanOptions.scanOptions().match(patterns).count(1000).build();
        Set<String> execute = stringRedisTemplate.execute(new RedisCallback<Set<String>>() {
            @Override
            public Set<String> doInRedis(RedisConnection connection) {

                Set<String> binaryKeys = new HashSet<>();

                Cursor<byte[]> cursor = connection.scan(scanOptions);
                while (cursor.hasNext()) {
                    binaryKeys.add(new String(cursor.next()));
                }
                return binaryKeys;
            }
        });
        log.info("getMusics, 根据音乐名查找的 keys :" + execute);
        // 再根据音乐名字获取到音乐 ID，从而获取到音乐详情
        List<String> list = stringRedisTemplate.opsForValue().multiGet(execute);
        // 得到音乐 ID 后，就是根据音乐 ID 获取音乐本体
        log.info("getMusics, 从 redis 中获取的音乐 ID 集合 musicIds: " + list);
        Set<Music> musicSet = new HashSet<>();
        List<Integer> musicIds = new ArrayList<>();
        if (list != null && list.size() > 0) {
            for (String musicId : list) {
                musicIds.add(Integer.valueOf(musicId));
                try {
                    String musicKey = "music:info:" + musicId;
                    Music musicRedis = objectMapper.readValue(stringRedisTemplate.opsForValue().get(musicKey), Music.class);
                    musicSet.add(musicRedis);
                } catch (JsonProcessingException e) {
                    throw new RuntimeException(e);
                }
            }
            log.info("getMusics, 从 redis 中获取的音乐集合 musics: " + musicSet);

            // TODO 然后再去 mysql 中查找剩下的数据，如果有歌曲名的话
            List<Music> result = musicService.getMusicsNotInMusicIdsAndTitleLike(musicIds, musicName);
            // TODO 把对应歌曲信息添加到 redis 中
            if (result != null && result.size() != 0) {
                log.info("getMusics, 音乐不在 redis 中存在，需要新增音乐到 redis 中：" + result);
                for (Music music : result) {
                    musicSet.add(music);
                    // 将对应音乐存入 redis 中
                    String musicKey = "music:info:" + music.getId();
                    String musicname = "musicName:" + music.getTitle();
                    try {
                        stringRedisTemplate.opsForValue().set(musicKey, objectMapper.writeValueAsString(music), Duration.ofDays(60));
                        stringRedisTemplate.opsForValue().set(musicname, String.valueOf(music.getId()), Duration.ofDays(60));
                    } catch (JsonProcessingException e) {
                        log.error(e.getMessage());
                    }
                }
            }
            return new ResponseBodyMessage<>(200, null, new ArrayList<>(musicSet));
        }


        // 此时说明 redis 中没有歌曲，则需要添加
        List<Music> result = musicService.getMusicsByName(musicName);
        if (result != null && result.size() != 0) {
            log.info("getMusics, 音乐不在 redis 中存在，需要新增音乐到 redis 中：" + result);
            for (Music music : result) {
                // 将对应音乐存入 redis 中
                String musicKey = "music:info:" + music.getId();
                String musicname = "musicName:" + music.getTitle();
                try {
                    stringRedisTemplate.opsForValue().set(musicKey, objectMapper.writeValueAsString(music), Duration.ofDays(60));
                    stringRedisTemplate.opsForValue().set(musicname, String.valueOf(music.getId()), Duration.ofDays(60));
                } catch (JsonProcessingException e) {
                    log.error(e.getMessage());
                }

            }
        }
        return new ResponseBodyMessage<>(200, null, result);

    }

    // 根据音乐ID 来查询音乐
    @RequestMapping("/getMusicById")
    public ResponseBodyMessage<Music> getMusics(@RequestParam Integer musicId) {
        if (musicId == null || musicId <= 0) {
            // musicId 不合法，返回
            return new ResponseBodyMessage<>(-1, "musicId 不合法", null);
        }
        // 先去 redis 中查询音乐，查不到再去 mysql 中查找
        String musicKey = "music:info:" + musicId;
        String musicValue = stringRedisTemplate.opsForValue().get(musicKey);
        log.info("getMusics, 从 redis 中查找音乐本体: " + musicValue);
        if (StringUtils.hasLength(musicValue)) {
            // 说明在 redis 中找到了音乐，直接返回该音乐即可
            try {
                Music musicRedis = objectMapper.readValue(musicValue, Music.class);
                return new ResponseBodyMessage<>(200, null, musicRedis);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        Music music = musicService.getMusicById(musicId);
        // 将信息存入 redis 中
        if (music != null) {
            try {
                // 将对应音乐存入 redis 中
                musicKey = "music:info:" + music.getId();
                String musicname = "musicName:" + music.getTitle();
                stringRedisTemplate.opsForValue().set(musicKey, objectMapper.writeValueAsString(music), Duration.ofDays(60));
                stringRedisTemplate.opsForValue().set(musicname, String.valueOf(music.getId()), Duration.ofDays(60));
                log.info("getMusics, 音乐不在 redis 中存在，需要新增音乐到 redis 中：" + music);
            } catch (JsonProcessingException e) {
                log.error(e.getMessage());
            }
            return new ResponseBodyMessage<>(200, null, music);
        }

        return new ResponseBodyMessage<>(-1, "获取歌失败", null);
    }


//    // 推荐歌曲
//    @RequestMapping("/recommendMusics")
//    public ResponseBodyMessage<List<Music>> recommendMusics(HttpServletRequest request) {
//        // TODO 拉取用户最近听的 100 首歌，根据用户 ID 联合查询 listen 表 以及 music 表，并按照时间逆序排序
//        // TODO 然后再遍历音乐集合，统计各个音乐标签的数量，并按照出现次数排序
//        // TODO 然后再根据音乐标签去数据库中查找音乐，根据排名来推荐 20 首音乐 recommends
//        // TODO 然后再来查询音乐 id 不在 recommends 中的音乐，musicId not in ()
//        // TODO 最后将两个音乐合集一合并，最后返回即可
//
//        // 检查是否登录
//        HttpSession session = request.getSession(false);
//        if (session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
//            return new ResponseBodyMessage<>(-2, "请登录后再操作", null);
//        }
//
//        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
//        // 拉取用户最近收听的 100 首歌
//        List<Music> music1 = musicService.getLast100ListenedMusic(user.getId());
//        log.info("最近 100 首音乐" + music1.toString());
//        if (music1 == null || music1.size() == 0) {
//            // 说明用户没有听过音乐
//            // 就可以直接推送库里的全部音乐
//            List<Music> result = musicService.getMusics();
//            return new ResponseBodyMessage<>(200, null, result);
//        }
//
//        // 根据 musicId 来去标签表获取标签，并统计出标签的出现次数，并按照出现次数排序，优先级队列
//        Map<String, Integer> labelMap = new HashMap<>();
//        for (Music music : music1) {
//            List<String> label = musicLabelService.getLabelByMusicId(music.getId());
//            for (String labelName : label) {
//                int count = labelMap.getOrDefault(labelName, 0);
//                labelMap.put(labelName, count + 1);
//            }
//        }
//        log.info("音乐标签" + labelMap);
//        // 建立大根堆, 并把元素放入堆中
//        PriorityQueue<Map.Entry<String, Integer>> labelQueue = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
//            @Override
//            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
//                return o2.getValue() - o1.getValue();
//            }
//        });
//        labelQueue.addAll(labelMap.entrySet());
//
//        log.info("排序后的音乐标签" + labelQueue);
//
//        // 然后再根据音乐标签，来去查找播放量较高的, 推荐够 20 首就行，
//        // 如果队列为空了还不够 20 首，那就能推荐多少就推荐多少
//        List<Music> musicRecommend = new ArrayList<>();
//        // 为了保证在前面的一定是点击量高的，并且还需要去重，所以将 musicTmp 作为这首歌是否出现过的标志
//        // 如果 musicTmp 出现过这首歌，则不能添加进最终的推荐结果集合里，反之则能加入
//        Set<Music> musicTmp = new HashSet<>();
//        while (!labelQueue.isEmpty()) {
//            if (!musicRecommend.isEmpty() && musicRecommend.size() >= 20) {
//                break;
//            }
//            Map.Entry<String, Integer> label = labelQueue.poll();
//            // 根据音乐标签，获取播放量高的音乐
//            List<Music> music = musicService.getHighViewCountMusicByLabelName(label.getKey());
//            if (music != null && music.size() != 0) {
//                for (Music m : music) {
//                    if (!musicTmp.contains(m)) {
//                        musicRecommend.add(m);
//                        musicTmp.add(m);
//                    }
//                }
//            }
//        }
//
//        log.info("推荐的音乐" + musicRecommend.toString());
//
//        // 然后去数据库里查询剩下的音乐, 首先获取以及推荐了的歌曲的 id
//        List<Integer> musicIds = new ArrayList<>();
//        for (Music music : musicRecommend) {
//            musicIds.add(music.getId());
//        }
//        List<Music> musicsNotInMusicIds = musicService.getMusicsNotInMusicIds(musicIds);
//        log.info("剩下的音乐合集: " + musicsNotInMusicIds);
//        // 再把剩下的音乐全部添加到推荐音乐合集的后面，最后返回即可
//        musicRecommend.addAll(musicsNotInMusicIds);
//        log.info("返回的推荐音乐 + 剩下的音乐合集: " + musicRecommend);
//
//        return new ResponseBodyMessage<>(200, null, musicRecommend);
//
//    }


    // 推荐歌曲
    @RequestMapping("/recommendMusics")
    public ResponseBodyMessage<List<Music>> recommendMusics(HttpServletRequest request) {
        // TODO 拉取用户最近听的 100 首歌，根据用户 ID 联合查询 listen 表 以及 music 表，并按照时间逆序排序
        // TODO 然后再遍历音乐集合，统计各个音乐标签的数量，并按照出现次数排序
        // TODO 然后再根据音乐标签去数据库中查找音乐，根据排名来推荐 20 首音乐recommends
        // TODO 然后再来查询音乐 id 不在 recommends 中的音乐，musicId not in ()
        // TODO 最后将两个音乐合集一合并，最后返回即可

        // 检查是否登录
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
            return new ResponseBodyMessage<>(-2, "请登录后再操作", null);
        }

        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
        // 拉取用户最近收听的 100 首歌
        // TODO 可以从 redis 中，找："user:1:listenmusic:27"  "27"   过期时间:30 天
        // TODO 获取到音乐 ID 后，就能去 redis 中查找对应音乐的标签 "musicLabel:music:27"  "二次元", "摇滚", "动漫"
        // TODO 先从 redis 中获取该用户的听歌标签排行榜，如果没有，才执行后面的步骤
        String userLabelRank = "user:" + user.getId() + ":recommendlabel:rank";
        Set<String> set = stringRedisTemplate.opsForZSet().reverseRange(userLabelRank, 0, -1);
        if (set != null && set.size() != 0) {
            // 说明得到了标签排行榜，则根据标签来查找推荐音乐即可
            log.info("得到指定用户标签排行榜: " + set);
            Set<Music> musicRecommend = new HashSet<>();
            for (String label : set) {
                // 根据音乐标签，获取播放量高的音乐
                if (!musicRecommend.isEmpty() && musicRecommend.size() >= 20) {
                    break;
                }
                List<Music> music = musicService.getHighViewCountMusicByLabelName(label);
                if (music != null && music.size() != 0) {
                    musicRecommend.addAll(music);
                }
            }
            log.info("redis 里的推荐音乐合集: " + musicRecommend);

            // 然后取数据库里查询剩下的音乐, 首先获取以及推荐了的歌曲的 id
            List<Integer> musicIds = new ArrayList<>();
            for (Music music : musicRecommend) {
                musicIds.add(music.getId());
            }
            // 如果 redis 里没有歌曲，则直接到 mysql 中获取所有歌曲
            List<Music> musicsNotInMusicIds = null;
            if (musicIds.size() > 0) {
                musicsNotInMusicIds = musicService.getMusicsNotInMusicIds(musicIds);
            } else {
                // 说明此时 redis 中什么音乐都没有，则需要去 mysql 中查找所有音乐
                musicsNotInMusicIds = musicService.getMusics();
            }

            log.info("剩下的音乐合集: " + musicsNotInMusicIds);
            // 再把剩下的音乐全部添加到推荐音乐合集的后面，最后返回即可
            List<Music> retMusic = new ArrayList<>();
            for (Music music : musicRecommend) {
                retMusic.add(music);
            }
            for (Music music : musicsNotInMusicIds) {
                retMusic.add(music);
            }
            log.info("返回的推荐音乐 + 剩下的音乐合集: " + retMusic);
            return new ResponseBodyMessage<>(200, null, retMusic);
        }


        // 到这里说明 redis 还没有这个用户的标签排行榜，所以从 redis 拉取 100 歌统计标签，再添加排行榜
        String listenmusicPattern = "user:" + user.getId() + ":listenmusic:*";

        List<String> keys = new ArrayList<>();
        log.info("recommendMusics, 根据样式: " + listenmusicPattern + "去 redis 中扫描符合要求的 keys");
        // 指定样式以及每次扫描的数量
        ScanOptions scanOptions = ScanOptions.scanOptions().match(listenmusicPattern).count(1000).build();
        Set<String> execute = stringRedisTemplate.execute(new RedisCallback<Set<String>>() {
            @Override
            public Set<String> doInRedis(RedisConnection connection) {

                Set<String> binaryKeys = new HashSet<>();

                Cursor<byte[]> cursor = connection.scan(scanOptions);
                while (cursor.hasNext()) {
                    binaryKeys.add(new String(cursor.next()));
                }
                return binaryKeys;
            }
        });
        log.info("recommendMusics, redis 根据指定音乐听歌记录查找的 keys :" + execute);
        // 然后根据 keys 获取到音乐 ID，再根据音乐 ID 获取音乐详情
        List<String> musicId = stringRedisTemplate.opsForValue().multiGet(execute);
        log.info("recommendMusics, redis 根据指定音乐听歌记录查找的 musicId :" + musicId);
        List<Music> musicRedis = new ArrayList<>();
        if (musicId != null && musicId.size() > 0) {
            for (String id : musicId) {
                String key = "music:info:" + id;
                try {
                    Music music = objectMapper.readValue(stringRedisTemplate.opsForValue().get(key), Music.class);
                    musicRedis.add(music);
                    if (musicRedis.size() >= 100) {
                        break;
                    }
                } catch (JsonProcessingException e) {
                    log.error(e.getMessage());
                }
            }
            log.info("recommendMusics, redis 根据指定音乐听歌记录查找的 musics :" + musicRedis);
            // 此时就获取到了近 30 天用户收听的 100 首歌曲
            // 然后统计每首歌曲的音乐标签，并且排序 "musicLabel:music:27"  "二次元", "摇滚", "动漫"
            Map<String, Double> labelMap = new HashMap<>();
            // 根据 musicId，去 redis 中查找对应的音乐标签
            for (Music music : musicRedis) {
                String labelMusicKey = "musicLabel:music:" + music.getId();
                Set<String> labelList = stringRedisTemplate.opsForSet().members(labelMusicKey);
                for (String label : labelList) {
                    Double value = labelMap.getOrDefault(label, 0.0);
                    labelMap.put(label, value + 1.0);
                }
                // TODO 如果 redis 中的对应音乐标签是空，则需要从数据库中得到音乐标签，并存入 redis 中
                if (labelList == null || labelList.isEmpty() || labelList.size() <= 0) {
                    List<String> labels = musicLabelService.getLabelByMusicId(music.getId());
                    for (String label : labels) {
                        // 将音乐标签也添加到 redis 中
                        String redisMusicLabel = "musicLabel:music:" + music.getId();
                        stringRedisTemplate.opsForSet().add(redisMusicLabel, label);
                        stringRedisTemplate.expire(redisMusicLabel, Duration.ofDays(60));
                        Double value = labelMap.getOrDefault(label, 0.0);
                        labelMap.put(label, value + 1.0);
                    }
                }
            }
            // 然后将其排序，可以用 redis 的 zset 来完成
            // 还是先约定 key: "user:1:recommendlabel:rank"  value: "50","二次元" "30","动漫"  "20","摇滚"
            String userRecommendLabelRank = "user:" + user.getId() + ":recommendlabel:rank";
            Set<ZSetOperations.TypedTuple<String>> userRecommendLabelRankValue = new HashSet<>();
            for (Map.Entry<String, Double> entry : labelMap.entrySet()) {
                ZSetOperations.TypedTuple<String> typedTuple = ZSetOperations.TypedTuple.of(entry.getKey(), entry.getValue());
                userRecommendLabelRankValue.add(typedTuple);
            }
            if (!userRecommendLabelRankValue.isEmpty()) {
                stringRedisTemplate.opsForZSet().add(userRecommendLabelRank, userRecommendLabelRankValue);
                stringRedisTemplate.expire(userRecommendLabelRank, Duration.ofDays(15));
            }
            // 此时再从 redis 中获取所有的标签，然后再根据标签推荐音乐
            set = stringRedisTemplate.opsForZSet().reverseRange(userRecommendLabelRank, 0, -1);
            // TODO 遍历标签，从 mysql 中获取推荐音乐
            log.info("得到指定用户标签排行榜: " + set);
            Set<Music> musicRecommend = new HashSet<>();
            for (String label : set) {
                // 根据音乐标签，获取播放量高的音乐
                if (!musicRecommend.isEmpty() && musicRecommend.size() >= 20) {
                    break;
                }
                List<Music> music = musicService.getHighViewCountMusicByLabelName(label);
                if (music != null && music.size() != 0) {
                    musicRecommend.addAll(music);
                }
            }
            // 然后取数据库里查询剩下的音乐, 首先获取以及推荐了的歌曲的 id
            List<Integer> musicIds = new ArrayList<>();
            for (Music music : musicRecommend) {
                musicIds.add(music.getId());
            }
            List<Music> musicsNotInMusicIds = musicService.getMusicsNotInMusicIds(musicIds);
            log.info("剩下的音乐合集: " + musicsNotInMusicIds);
            // 再把剩下的音乐全部添加到推荐音乐合集的后面，最后返回即可
            List<Music> retMusic = new ArrayList<>();
            for (Music music : musicRecommend) {
                retMusic.add(music);
            }
            for (Music music : musicsNotInMusicIds) {
                retMusic.add(music);
            }

            log.info("返回的推荐音乐 + 剩下的音乐合集: " + retMusic);
            return new ResponseBodyMessage<>(200, null, retMusic);
        }


        // 代码走到这里，说明 redis 里没有关于该用户听歌的记录以及标签排行榜，则需要添加
        List<Music> music1 = musicService.getLast100ListenedMusic(user.getId());
        log.info("最近 100 首音乐" + music1.toString());
        if (music1 == null || music1.size() == 0) {
            // 说明用户没有听过音乐
            // 就可以直接推送库里的全部音乐
            List<Music> result = musicService.getMusics();
            return new ResponseBodyMessage<>(200, null, result);
        }

        // 根据 musicId 来去标签表获取标签，并统计出标签的出现次数，并按照出现次数排序，优先级队列
        Map<String, Integer> labelMap = new HashMap<>();
        for (Music music : music1) {
            List<String> label = musicLabelService.getLabelByMusicId(music.getId());
            for (String labelName : label) {
                int count = labelMap.getOrDefault(labelName, 0);
                labelMap.put(labelName, count + 1);
            }
        }
        log.info("音乐标签" + labelMap.toString());
        // 建立大根堆, 并把元素放入堆中
        PriorityQueue<Map.Entry<String, Integer>> labelQueue = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                return o2.getValue() - o1.getValue();
            }
        });
        labelQueue.addAll(labelMap.entrySet());

        log.info("排序后的音乐标签" + labelQueue.toString());
        // TODO 然后将标签排行榜添加到 redis 中
        String userRecommendLabelRank = "user:" + user.getId() + ":recommendlabel:rank";
        Set<ZSetOperations.TypedTuple<String>> userRecommendLabelRankValue = new HashSet<>();
        for (Map.Entry<String, Integer> entry : labelMap.entrySet()) {
            ZSetOperations.TypedTuple<String> typedTuple = ZSetOperations.TypedTuple.of(entry.getKey(), (entry.getValue()) * 1.0);
            userRecommendLabelRankValue.add(typedTuple);
        }
        log.info("将指定用户标签排行榜添加到 redis 中: " + userRecommendLabelRankValue);
        stringRedisTemplate.opsForZSet().add(userRecommendLabelRank, userRecommendLabelRankValue);
        stringRedisTemplate.expire(userRecommendLabelRank, Duration.ofDays(15));


        // 然后再根据音乐标签，来去查找播放量较高的, 推荐够 20 首就行，
        // 如果队列为空了还不够 20 首，那就能推荐多少就推荐多少
        Set<Music> musicRecommend = new HashSet<>();
        while (!labelQueue.isEmpty()) {
            if (!musicRecommend.isEmpty() && musicRecommend.size() >= 20) {
                break;
            }
            Map.Entry<String, Integer> label = labelQueue.poll();
            // 根据音乐标签，获取播放量高的音乐
            List<Music> music = musicService.getHighViewCountMusicByLabelName(label.getKey());
            if (music != null && music.size() != 0) {
                musicRecommend.addAll(music);
            }
        }

        log.info("推荐的音乐" + musicRecommend.toString());

        // 然后取数据库里查询剩下的音乐, 首先获取以及推荐了的歌曲的 id
        List<Integer> musicIds = new ArrayList<>();
        for (Music music : musicRecommend) {
            musicIds.add(music.getId());
        }
        List<Music> musicsNotInMusicIds = musicService.getMusicsNotInMusicIds(musicIds);
        log.info("剩下的音乐合集: " + musicsNotInMusicIds);
        // 再把剩下的音乐全部添加到推荐音乐合集的后面，最后返回即可
        List<Music> retMusic = new ArrayList<>();
        for (Music music : musicRecommend) {
            retMusic.add(music);
        }
        for (Music music : musicsNotInMusicIds) {
            retMusic.add(music);
        }

        log.info("返回的推荐音乐 + 剩下的音乐合集: " + retMusic);

        return new ResponseBodyMessage<>(200, null, retMusic);

    }

}
